#include "SPage.h"

#include <stdlib.h>
#include <string.h>

SPage::SPage(BufferFrame& page, SPSegment& segment) : segment(segment), page(page), header(*(Header*)(page.getData()))
{
    //An dieser Stelle wird der Header ausgelesen. Man kann davon ausgehen, dass der Header auf der Page sinnvoll existiert.
}


/**
 * Erstellt eine slotted Page anhand einer bereits als slotted Page verwendeten Seite.
 * Die Headerstruktur muss bereits auf der Seite existieren.
 */
SPage SPage::fromExisting(BufferFrame& page, SPSegment& segment)
{
    // Die Seite besitzt bereits einen Header, er muss nur noch ausgelesen werden (Passiert dann im Konstruktor).
    return SPage(page, segment);
}


/**
 * Erstellt eine slotted Page anhand einer noch leeren Seite.
 * Die Headerstruktur wird erstellt.
 */
SPage SPage::fromNew(BufferFrame& page, SPSegment& segment)
{
    // An dieser Stelle muss sofort die Page mit einem Initial-Header beschrieben werden, dieser wird im Konstruktor nämlich ausgelesen.
    Header& header = *(Header*)(page.getData());
    header.slotCount = 0;
    header.dataStart = pageSize;
    header.firstFreeSlot = 0;
    return SPage(page, segment);
}


/**
 * HILFSFUNKTION
 * Ermittelt den globalen Zeiger auf den Seitenanfang.
 */
char* SPage::getGlobalPageStart()
{
    return (char*)(&(this->header));
}


/**
 * HILFSFUNKTION
 * Gibt die Referenz auf einen Slot anhand der id zurück.
 */
SPage::Slot& SPage::getSlot(unsigned slotId)
{
    if (slotId >= this->header.slotCount)
    {
        cout << "Something bad happened: Slot " << slotId << " out of Range." << endl;
        exit(1);
    }
    return *(Slot*)(this->getGlobalPageStart() + sizeof(Header) + (slotId * sizeof(Slot)));
}


/**
 * HILFSFUNKTION
 * Ermittelt die Größe des freien Speichers zwischen Slots und dem dataStart-Pointer.
 */
unsigned SPage::getFreeSpaceOnPage()
{
    return this->header.dataStart - (sizeof(Header) + (this->header.slotCount * sizeof(Slot)));
}


/**
 * Gibt die Daten eines bestimmten Slots zurück. Dabei können die Daten auf dieser oder einer
 * anderen Seite liegen.
 */
Record SPage::get(unsigned slotId)
{
    // Gibt den Record aus dem Slot zurück

    Slot& slot = this->getSlot(slotId);

    // Slot leer
    if (slot.type==Slot::empty)
    {
        // Nur zum Test:
        return Record(0, NULL);
        // TODO: einkommentieren!!!
        //cout << "Something bad happened: Slot " << i << " out of Range." << endl;
        //exit(1);
    }

    // nicht lokal vorhanden
    if (slot.type==Slot::tid)
    {
        TID tid;
        tid.pageId = slot.offset;
        tid.slotId = slot.length;
        return this->segment.lookup(tid);
    }

    // lokal vorhanden
    unsigned len = slot.length;
    char* data = this->getGlobalPageStart() + slot.offset;
    return Record(len, data);
}


/**
 * Säubert einen Slot: Der Slot wird als leer markiert und der von ihm belegte Speicher
 * somit freigegeben. Handelt es sich um den vordesten Datensatz auf der Seite, so wird
 * der dataStart-Zeiger auf den nächsten Datensatz aktualisiert.
 * Der Slot besteht anschließend weiterhin!
 * true, falls erfolgreich. false, falls Slot bereits leer.
 */
bool SPage::clear(unsigned slotId)
{
    // Leert einen slot und aktualisiert den Header

    Slot& slot = this->getSlot(slotId);

    // Slot bereits leer
    if (slot.type==Slot::empty) return false;

    // ggf dataStart aktualisieren
    if (slot.type==Slot::normal && slot.offset == this->header.dataStart)
    {
        // Die zu löschenden Daten waren die vordersten auf dieser Seite -> nächste "vordersten" Daten suchen
        unsigned offset = pageSize;
        for (unsigned j=0; j<this->header.slotCount; j++)
        {
            if (slotId==j) continue;
            Slot& tmpSlot = this->getSlot(j);
            if (tmpSlot.type==Slot::normal && tmpSlot.offset<offset) offset = tmpSlot.offset;
        }
        this->header.dataStart = offset;
    }

    slot.length = 0;
    slot.offset = 0;
    slot.type = Slot::empty;

    // ggf firstFreeSlot nach vorne aktualisieren
    if (slotId < this->header.firstFreeSlot) this->header.firstFreeSlot = slotId;

    return true;
}


/**
 * Versucht, den Record in den vorderen freien Bereich (vor dataStart) einzufügen.
 * Falls kein Slot frei ist, wird ein neuer erzeugt.
 * Falls nicht genug freier Platz zur Verfügung steht, wird -1 zurückgegeben.
 */
signed SPage::insert(const Record& r)
{
    // Speichert einen Record in einem Slot ab und aktualisiert den Header

    // noch freier Slot? wenn nicht, muss die größe eines neuen Slots berücksichtigt werden
    bool newSlot = false;
    if (this->header.firstFreeSlot==this->header.slotCount)
    {
        newSlot = true;
    }

    // Genug Platz zwischen Slots und dataStart?
    signed space = this->getFreeSpaceOnPage() - (newSlot ? sizeof(Slot) : 0);
    if (space>=(signed)r.getLen())
    {
        unsigned slotId = this->header.firstFreeSlot;
        // Slot existiert noch nich:
        if (newSlot)
        {
            this->header.slotCount += 1;
            this->header.firstFreeSlot += 1;
        }
        else
        {
            // neuen firstFreeSlot finden
            bool found = false;
            for (unsigned i=(slotId+1); i<this->header.slotCount; i++)
            {
                Slot& s = this->getSlot(i);
                if (s.offset==0 && s.length==0)
                {
                    this->header.firstFreeSlot = i;
                    found = true;
                    break;
                }
            }
            if (!found) this->header.firstFreeSlot = this->header.slotCount;
        }

        Slot& slot = this->getSlot(slotId);
        slot.offset = this->header.dataStart - r.getLen();
        slot.length = r.getLen();
        slot.type = Slot::normal;
        memcpy(this->getGlobalPageStart() + slot.offset, r.getData(), r.getLen());
        this->header.dataStart = slot.offset;

        return slotId;
    }

    // TODO: noch Platz zwischen Daten (HINTER dataStart), so kann dies an dieser Stelle berücksichtigt werden

    return -1;
}


/**
 * Überschreibt die Daten eines bestimmten Slots.
 *
 * Befinden sich die Daten bisher auf dieser Seite, werden folgende Schritte versucht
 * (erst passender wird ausgeführt):
 * - kann der Record einfach nach hinten verlängert werden?
 * - hat der Record nach hinten UND vorne insgesamt noch genügend Platz? (Positionierung so,
 *   dass, das Ende möglichst weit hinten abschließt)
 * - passt der Record in den vorderen freien Speicherbereich?
 * - ansonsten wird der Record auf eine beliebige andere Seite gespeichert und der Slot
 *   als "isTid" markiert.
 *
 * Befinden sich die Daten bisher auf einer anderen Seite ("isTid" gesetzt), so wird
 * folgendes unternommen (erst passendes wird ausgeführt):
 * - TODO: kann der Record doch wieder auf der "Hauptseite" gespeichert werden?
 * - klappt das Updaten auf der anderen Seite? (Schritte siehe oben)
 * - ansonsten wird der Record von der Seite gelöscht und auf eine beliebige andere gespeichert.
 */
bool SPage::update(unsigned slotId, const Record& r, bool allowOutsourcing)
{
    Slot& slot = this->getSlot(slotId);

    // Daten auf anderer Seite?
    if (slot.type==Slot::tid)
    {
        // TODO: Falls mal defragmentiert wird, sollte man hier noch überprüfen, ob die Daten doch wieder in diese Page passen

        TID tid;
        tid.pageId = slot.offset;
        tid.slotId = slot.length;

        bool result = this->segment.update(tid, r, false);

        // Klappt das updaten auf der anderen Seite?
        if (result) return true;

        // Auf der einen Seite löschen und auf andere freie Seite speichern
        this->segment.deleteRecord(tid);
        TID refTid = this->segment.insert(r);
        slot.offset = refTid.pageId;
        slot.length = refTid.slotId;
    }

    // Daten auf aktueller Seite

    // Sollen Daten "nur" verkürzt werden?
    if (slot.length>=r.getLen())
    {
        slot.length = r.getLen();
        memcpy(this->getGlobalPageStart() + slot.offset, r.getData(), r.getLen());
        return true;
    }

    // Verlängerung nötig

    // Maximale freie Grenzen um den aktuellen Speicherort bestimmen
    unsigned freeFrom = sizeof(Header) + (this->header.slotCount * sizeof(Slot));
    unsigned freeTo = pageSize;
    for (unsigned i=0; i<this->header.slotCount; i++)
    {
        if (slotId==i) continue;
        Slot& tmpSlot = this->getSlot(i);
        if (tmpSlot.type!=Slot::normal) continue;
        if (tmpSlot.offset>slot.offset && tmpSlot.offset<freeTo) freeTo = tmpSlot.offset;
        else if (tmpSlot.offset<slot.offset && (tmpSlot.offset+tmpSlot.length)>freeFrom) freeFrom = tmpSlot.offset+tmpSlot.length;
    }

    // genug Platz an aktueller Stelle (dahinter)?
    if (freeTo - slot.offset >= r.getLen())
    {
        slot.length = r.getLen();
        memcpy(this->getGlobalPageStart() + slot.offset, r.getData(), r.getLen());
        return true;
    }

    // genug Platz an aktueller Stelle (davor + dahinter)?
    if (freeTo - freeFrom >= r.getLen())
    {
        // War der Datensatz der vorderste (am nächsten zu den Slots)?
        if (this->header.dataStart==slot.offset) this->header.dataStart = freeTo - r.getLen();

        slot.offset = freeTo - r.getLen();
        slot.length = r.getLen();
        memcpy(this->getGlobalPageStart() + slot.offset, r.getData(), r.getLen());
        return true;
    }

    // Im vorderen freien Bereich genug Platz?
    if (this->getFreeSpaceOnPage()>=r.getLen())
    {
        this->clear(slotId);

        slot.offset = this->header.dataStart - r.getLen();
        slot.length = r.getLen();
        memcpy(this->getGlobalPageStart() + slot.offset, r.getData(), r.getLen());
        this->header.dataStart = slot.offset;

        return true;
    }


    // Dürfen Datens NICHT auf andere Seite ausgelagert werden, dann ergeht hier
    // die Mitteilung, dass das Update auf dieser Seite nicht möglich ist.
    if (!allowOutsourcing) return false;

    // Andere Seite finden, wo genügend Platz ist
    this->clear(slotId);
    TID tid = this->segment.insert(r);
    slot.offset = tid.pageId;
    slot.length = tid.slotId;
    slot.type = Slot::tid;
    return true;
}


/**
 * Liefert eine Referenz auf die "normale" Seite, auf der diese Slotted Page gespeichert ist.
 */
BufferFrame& SPage::getPage()
{
    return this->page;
}
